# -*- coding:utf-8 -*-
"""
@FileName  :etf_cta_strategy_shenshu.py
@Time      :2022/10/27 10:44
@Author    :fsksf

ETF 赎回示例
"""
import datetime
import time
from enum import Enum
from typing import Any, Callable, Dict
from vnpy.trader.utility import ArrayManager, BarGenerator
from vnpy.trader.object import BarData, TickData, OrderData, TradeData
# 注意这里引入的是ETFTemplate
from vnpy_ctastrategy.etf_template import ETFTemplate


class SSMode(Enum):
    DISCOUNT = "折价"
    PREMIUM = "溢价"
    PREMIUMING = "申购"
    DISCOUNTING = "赎回"
    FLAT = "平仓"
    NONE = "无操作"


class ETFBSStrategyShenShuYS(ETFTemplate):

    # 限定开仓交易时间，防止尾盘开仓
    trade_morning_start = datetime.time(hour=9, minute=30)      # 上午开始时间
    trade_morning_end = datetime.time(hour=11, minute=30)       # 上午结束时间
    trade_afternoon_start = datetime.time(hour=13, minute=00)   # 下午开始时间
    trade_afternoon_end = datetime.time(hour=14, minute=59)     # 下午结束时间

    # 启动策略时的配置项
    trade_basket = True
    discount_min_profit: float = 500                        # 折价最小利润 =
    premium_min_profit: float = 500                         # 溢价最小利润
    pre_ss_vol: int = 900000                                # 申赎最小单位
    per_order_vol: int = 1000000                            # 每次买卖ETF的最大数量，拆单，每次委托最高一百万(柜台规则)
    target_pos: float = 0

    parameters = ['pre_ss_vol', "per_order_vol",
                  'discount_min_profit', 'premium_min_profit', 'target_pos', 'flag', 'level_1_factor', 'level_2_factor']

    #仓位因子参数
    level_1_factor = 'spBasket'  # 第一参数
    level_2_factor = '159629'  # 第二参数
    flag = 5


    def __init__(
        self,
        cta_engine: Any,
        strategy_name: str,
        vt_symbol: str,         # 篮子对应的ETF
        setting: dict,
    ):
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)
        self.trade_basket = True
        self.tick_am = []
        self.am = ArrayManager(size=100)
        self.bg = BarGenerator(on_bar=self.on_bar)
        self.tick: TickData = None
        self.last_order_time = None
        self.mode: SSMode = SSMode.NONE                 # 当前模式：标识当前是折价交易、溢价交易状态

    def on_init(self):
        self.load_bar(1)

    def on_tick(self, tick: TickData):
        """
        这里之做bar的合成
        :param tick:
        :return:
        """
        self.print("on_tick -------------------------")
        self.print(tick)
        self.tick = tick
        self.bg.update_tick(tick)
        if not self.trading:
            return
        self.handle_tick(tick)

    def handle_tick(self, tick):
        """
        tick级别的策略逻辑写在这里
        :param tick:
        :return:
        """



        # 获取仓位因子
        pos_factor = self.get_pos_factor()

        level_1_factor_data = pos_factor.get(self.level_1_factor, None)
        if not level_1_factor_data:
            print(f'{self.level_1_factor} 仓位参数为空！')
            return

        level_2_factor_data = level_1_factor_data.get(self.level_2_factor)
        if not level_2_factor_data:
            print(f'{self.level_1_factor}:{self.level_2_factor} 仓位参数为空！')
            return
        factor_trade = level_2_factor_data['trade']
        factor_target_pos = float(level_2_factor_data['pos'])
        factor_algo = level_2_factor_data['algo']
        factor_interval = time.time() - level_2_factor_data['tm']  # 多久（秒）之前更新的仓位因子
        limit_buy_price = float(level_2_factor_data['buy_price'])
        limit_sell_price = float(level_2_factor_data['sell_price'])

        self.print(f"仓位因子 {factor_target_pos} ， {factor_algo} ， {limit_buy_price}， {limit_sell_price}")

        # 更新显示数据
        if self.target_pos != factor_target_pos or self.flag != factor_algo or \
                self.premium_min_profit != limit_sell_price or self.discount_min_profit != limit_buy_price:
            self.target_pos = factor_target_pos
            self.discount_min_profit = limit_buy_price
            self.premium_min_profit = limit_sell_price
            self.flag = factor_algo
            self.put_event()

        # 1.是否有未完成的订单
        if not self.all_order_finished():
            if self.last_order_time and time.time() - self.last_order_time > 60:    # 委托撤单等待时间
                self.cancel_all()
            return
        # 2.下单后等待一段时间，保证trade已经返回
        if self.last_order_time and time.time() - self.last_order_time < 8:
            return

        # 3.获取瞬时利润

        # 4.平仓逻辑, 未做与计算瞬时利润时价格的检验
        if (self.mode == SSMode.DISCOUNT or self.mode == SSMode.NONE or (self.mode == SSMode.FLAT and self.basket_pos == 0) ) and self.etf_pos >= 1 and self.flag == 4 and self.all_pos >= self.target_pos + 1:
            self.print("赎回")
            self.mode = SSMode.DISCOUNTING
            self.redemption(volume=self.pre_ss_vol)
            return
        elif self.mode == SSMode.DISCOUNTING and self.check_basket_req_bs(target_volume=0):
            self.print("卖出篮子")
            self.mode = SSMode.FLAT
            self.set_basket_target(target_volume=0)
            return
        elif self.mode == SSMode.PREMIUM and self.basket_pos >= 0.95:
            self.print("申购")
            self.mode = SSMode.PREMIUMING
            self.purchase(volume=self.pre_ss_vol)
            return
        elif (self.mode == SSMode.PREMIUMING or self.mode == SSMode.NONE) and self.pos.get(self.vt_symbol, 0) >= 100 and self.flag == 3 and self.all_pos >= self.target_pos + 1:
            self.print("卖出ETF")
            self.mode =SSMode.FLAT
            self.buy_sell_with_target(limit_price=tick.ask_price_1,
                                      target_volume=self.target_pos,
                                      per_order_max=self.per_order_vol)
            return
        elif self.all_pos == self.target_pos and not self.check_basket_req_bs(target_volume=0) and self.mode == SSMode.FLAT:
            # 平仓结束, 可以进一步开仓， 不return
            self.mode = SSMode.NONE
            self.print(f"mode status: {self.mode}")

        self.print(f"all pos: {self.all_pos}")
        # 5.开仓逻辑
        time_now = tick.datetime.time()
        # 限制开仓时间
        if self.trade_morning_start <= time_now <= self.trade_morning_end or \
                self.trade_afternoon_start <= time_now <= self.trade_afternoon_end:
            if self.flag == 4 and self.all_pos + 1 <= self.target_pos:         # 折价+现金差：买ETF、赎回、卖出篮子
                self.print("折价开仓: 买入ETF")
                if self.buy_sell_with_target(limit_price=tick.bid_price_1,
                                             target_volume=self.target_pos,
                                             per_order_max=self.per_order_vol):
                    # 开仓成功
                    self.mode = SSMode.DISCOUNT
                    return

            elif self.flag == 3 and self.all_pos + 1 <= self.target_pos:         # 溢价-现金差：买入篮子、申购、卖出ETF
                self.print("溢价开仓：买入篮子")
                if self.set_basket_target(target_volume=1):
                    self.mode = SSMode.PREMIUM
                return
        else:
            self.print("当前时间不允许开仓")
            return

        # 6. 开仓不完全： 处理开仓只成交了一部分的情况
        if self.mode == SSMode.DISCOUNT and self.etf_pos < 1:
            self.print("ETF开仓数量不够，直接补齐")
            self.buy_sell_with_target(limit_price=tick.bid_price_1,
                                      target_volume=1,
                                      per_order_max=self.per_order_vol)
        elif self.mode == SSMode.PREMIUM and self.check_basket_req_bs(target_volume=1):
            self.print("篮子开仓数量不够，直接补齐")
            self.set_basket_target(target_volume=1)

        # 7. 平仓不完全： 处理平仓只成交了一部分的情况
        if self.mode == SSMode.FLAT and self.pos.get(self.vt_symbol,0) >= 100 and self.flag == 3:
            self.print("卖出ETF")
            self.buy_sell_with_target(limit_price=tick.bid_price_1,
                                      target_volume=self.target_pos,
                                      per_order_max=self.per_order_vol)

        if self.mode == SSMode.FLAT and self.check_basket_req_bs(target_volume=0) and self.flag == 4:
            self.print("卖出篮子")
            self.set_basket_target(target_volume=0)

    def on_bar(self, bar: BarData):
        """
        这里只做历史数据队列的存储
        :param bar:
        :return:
        """
        self.am.update_bar(bar)
        if not self.am.inited:
            return
        if not self.trading:
            return
        self.handle_bar(bar)

    def handle_bar(self, bar: BarData):
        """
        bar级别的策略逻辑可以写在这里
        :param bar:
        :return:
        """
        pass
        # self.cancel_all()
        # # spread = self.get_spread("IM159845-1")
        # etf_symbol = self.vt_symbol
        # dDiscount, dPremium = self.get_moment_profit(etf_symbol)
        # # print(f'cta策略中取到了 价差：{spread.name} 价格为：{spread.last_price}')
        # print(f'cta策略中取到了 {etf_symbol} 的瞬时利润： discount: {dDiscount}, premium: {dPremium}')
        #
        # if self.basket_pos < 1 and self.etf_pos == 0:
        #     print('买入ETF')
        #     self.buy_sell_with_target(limit_price=self.tick.ask_price_1,
        #                               target_volume=self.pre_ss_vol,
        #                               per_order_max=self.pre_ss_vol,
        #                               )
        #
        # elif self.etf_pos >= self.pre_ss_vol:
        #     print('赎回')
        #     self.redemption(1)
        # elif self.basket_pos >= 1:
        #     print('卖出篮子')
        #     self.set_basket_target(target_volume=0)

    def print(self, msg):
        print(f"{datetime.datetime.now()} {self.strategy_name}: {msg}")
